LOADING...

loading

vue使用防抖函数时出现的this丢失问题

接到一个需求,滚动一次鼠标,改变时间轴刻度。因为鼠标滚动一次会触发多次滚动事件,为了节约性能,就想到使用防抖。

这是我从网上随便摘的一段,看起来没什么问题:

export function debounce(func, wait = 500) {
    let timer;
 
    return (...args) => {
        let context = this;
 
        if (timer) {
            clearTimeout(timer);
        }
         
        timer = setTimeout(function () {
            func.apply(context, args);
        }, wait);
    }
}

在vue里面使用:

// 滚动鼠标变化刻度,防抖。
changeTimeline: debounce(function (e) {
    if (e.deltaY > 0) {
        // 向上滚动
        this.timelineType > 1 && this.setTimelineType(this.timelineType - 1);
    } else {
        // 向下滚动
        this.timelineType < 3 && this.setTimelineType(this.timelineType + 1);
    }
}, 500),

然而鼠标滚动时报了这个错:this丢失了

图片

在网上查找各种方法,发现下面这样写没问题:在created()里把changeTimeline 重新包装一下

解决方法一
created() {
    this.changeTimeline = debounce(this.changeTimeline, 1000);
},
 
methods: {
    changeTimeline (e) {
        if (e.deltaY > 0) {
            this.timelineType > 1 && this.setTimelineType(this.timelineType - 1);
        } else {
            this.timelineType < 3 && this.setTimelineType(this.timelineType + 1);
        }
    }
}

问题是解决了,但是为什么呢?而且总觉得这不是最好的解决办法。

在网上查了各种有关this丢失的原因,最后发现问题出在debounce函数里返回函数使用了箭头函数,把返回函数改成普通函数的写法就没问题了。

解决方法二
export function debounce(func, wait = 500) {
    let timer;
 
    return function(...args) {
        let context = this;
 
        if (timer) {
            clearTimeout(timer);
        }
 
        // setTimeout写成箭头函数还是普通函数都可以
        timer = setTimeout(function () {
            func.apply(context, args);
        }, wait);
    }
}

这里箭头函数导致this丢失的原因是,箭头函数是根据词法作用域,也就是箭头函数定义时的位置来决定this。在声明changeTimeline函数时,debounce内部的this还是undefined,它返回的箭头函数this继承了debouncethis,所以在鼠标滚动时调用这个箭头函数this还是undefined

而普通函数的this是由代码运行时该函数的调用位置决定的。把debounce函数改成返回一个普通函数function(){},鼠标滚动时,调用该函数的组件实例已经创建完成,所以这个函数的thisVue实例。

如果debounce返回箭头函数,就在created()里把changeTimeline 重新包装一下,这样debounce绑定的就是Vue实例,而返回的箭头函数会继承这个this

this机制——箭头函数

this机制——this的4种绑定规则

javascript作用域——词法作用域和动态作用域